#pragma once

#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QHostAddress>
#include <QString>
#include <QByteArray>
#include <QMutex>
#include <QMutexLocker>
#include <QNetWorkInterFace>

#include <QDebug>

#include <gameboycore/link_cable.h>

class TcpServer : public QObject
{
	Q_OBJECT

signals:
	void clientConnected();

	void recieved(quint8);

public:
	TcpServer(QObject* parent = nullptr) :
		QObject(parent),
		m_server(new QTcpServer(this)),
		m_socket(nullptr),
		is_connected(false)
	{
		connect(m_server, &QTcpServer::newConnection, this, &TcpServer::newConnection);
		start();

		link_cable.setLink1RecieveCallback(std::bind(&TcpServer::link1RecieveCallback, this, std::placeholders::_1));
		link_cable.setLink2RecieveCallback(std::bind(&TcpServer::link2RecieveCallback, this, std::placeholders::_1));
	}

	virtual ~TcpServer()
	{
		if (m_server->isListening())
		{
			m_server->close();
		}
	}

	void start()
	{
		const QHostAddress address(getIpAddress());
		quint16 port(11412);

		if (!m_server->listen(address, port))
		{
			qDebug() << "Could not bind to " << address << ":" << QString(port);
		}
	}

public slots:
	void newConnection()
	{
		qDebug() << "Handling new connection";

		// can only handle one connection at a time
		if (is_connected) return;

		m_socket = m_server->nextPendingConnection();

		connect(m_socket, &QTcpSocket::readyRead, this, &TcpServer::readyRead);
		connect(m_socket, &QTcpSocket::disconnected, this, &TcpServer::disconnected);

		is_connected = true;

		emit clientConnected();
	}

	void readyRead()
	{
		QByteArray data = m_socket->readAll();

		if (data.size() >= 2)
		{
			QMutexLocker lock(&cable_mutex_);

			auto byte = (quint8)data.at(0);
			auto mode = static_cast<gb::Link::Mode>(data.at(1));

			link_cable.link2ReadyCallback(byte, mode);
		}
	}

	void disconnected()
	{
		is_connected = false;
		m_socket->deleteLater();
	}

	void linkReady(quint8 byte, gb::Link::Mode mode)
	{
		QMutexLocker lock(&cable_mutex_);
		link_cable.link1ReadyCallback(byte, mode);
	}

private:
	void link2RecieveCallback(quint8 byte)
	{
		QByteArray data;
		data.append(byte);

		m_socket->write(data);
	}

	void link1RecieveCallback(quint8 byte)
	{
		emit recieved(byte);
	}

	QString getIpAddress() const
	{
		auto addresses = QNetworkInterface::allAddresses();

		for (auto iter = addresses.begin(); iter != addresses.end(); ++iter)
		{
			if (iter->protocol() == QAbstractSocket::IPv4Protocol && !iter->isLoopback())
			{
				return iter->toString();
			}
		}

		return QString();
	}

private:
	QTcpServer* m_server{ nullptr };
	QTcpSocket* m_socket{ nullptr };

	bool is_connected;

	gb::LinkCable link_cable;
	QMutex cable_mutex_;
};
